#!/bin/sh
#############################################################################
##
##  This file is part of GAP, a system for computational discrete algebra.
##
##  Copyright of GAP belongs to its developers, whose names are too numerous
##  to list here. Please refer to the COPYRIGHT file for details.
##
##  SPDX-License-Identifier: GPL-2.0-or-later
##
##
#W  gac, the GAP compiler
##
##  gac [-d] [-c|-C] [-o <output>] {-f<option>} <input>...
##
##  'gac'  compiles the input files.   Input files  must be  GAP  source code
##  (suffix '.g' or '.gap'),  C source code (suffix '.c'),  or compiled  code
##  files (suffix '.o').
##
##  If '-d' is given then the code is compiled for dynamic loading
##  
##  If  neither '-c' nor '-C'  is given,  then 'gac'  compiles the code completely,
##  producing a  new kernel for static compilation or a dynamically loadable '.so'
##  file for dynamic compilation.
##
##  If '-c' is given,  then 'gac' only compiles the input files to 
##  '.o' object files, which must be further linked to make a static kernel or
##  dynamically loadable module
##
##  If '-C is given, then 'gac' only compiles the input files to C code, which 
##  will require compilation and linking to be usable.
##
##  If '-r' is given, then statically compiled files will be assumed to be given
##  by pathnames relative to the GAP root, and will be compiled for automatic loading
##  when files are sought relative to the GAP root.
##
##  The option '-o <output>' tells 'gac' to name the output file <output>.
##
##  The option '-save-temps' tells 'gac' to not delete any intermediate files
##
##  The option '-p <option>' tells 'gac' to pass the option  <option> to  the
##  C compiler.
##
##  The option '-P <option>' tells 'gac' to pass the option  <option> to  the
##  C linker.
##
##  The option '-L <option>' tells 'gac' to pass the option  <option>
##  to the C linker when linking dynamic modules. Contrary to -P the
##  option is appended at the end of the link command after the .o
##  files to link.
##
##  other options:
##   -k|--gap-compiler
##

SHELL="@SHELL@"

# absolute path of the directory in which GAP was compiled
abs_top_builddir="@abs_top_builddir@"

# path of the directory the GAP sources contained in
abs_top_srcdir="@abs_top_srcdir@"

#
libdir="@libdir@"

# path to the GAP executable
gap_compiler="${abs_top_builddir}/gap"

libtool="$SHELL ${abs_top_builddir}/libtool"

# read sysinfo.gap, which should set GAP_CFLAGS, GAP_CPPFLAGS, etc.
. "${abs_top_builddir}/sysinfo.gap"

# These three should be filled in by the standard autoconf procedures
c_compiler="$libtool --mode=compile $GAP_CC"
cxx_compiler="$libtool --mode=compile $GAP_CXX"
c_linker="$libtool --mode=link $GAP_CXX"

# These will need special care
c_dyn_linker="$libtool --mode=link $GAP_CXX -module -avoid-version -rpath $libdir"
c_addlibs=""

SYS_IS_CYGWIN32=@SYS_IS_CYGWIN32@
if [ X"$SYS_IS_CYGWIN32" = X"yes" ] ; then
    c_dyn_linker="$c_dyn_linker -no-undefined -version-info 0:0:0"
    # FIXME: use correct DLL path (also after "make install")
    c_dyn_linker="$c_dyn_linker -Wl,${abs_top_builddir}/bin/${GAParch}/gap.dll"
fi


#############################################################################
##
#F  gap_compile <output> <input> <module-name> <identifier>
##
gap_compile () {
    echo ${gap_compiler} -C $1 $2 $3 $4
    ${gap_compiler} -C "$1" "$2" "$3" "$4"
}


#############################################################################
##
#F  c_compile   <output> <input> <options>
##
c_compile () {
    echo ${c_compiler} $3 -o $1 ${GAP_CPPFLAGS} -c $2
    ${c_compiler} $3 -o $1 ${GAP_CPPFLAGS} -c $2 || exit 1
}


#############################################################################
##
#F  cxx_compile <output> <input> <options>
##
cxx_compile () {
    echo ${cxx_compiler} $3 -o $1 ${GAP_CPPFLAGS} -c $2
    ${cxx_compiler} $3 -o $1 ${GAP_CPPFLAGS} -c $2 || exit 1
}


#############################################################################
##
#F  c_link_dyn <output> <input>
##
c_link_dyn () {
    echo ${c_dyn_linker} ${GAP_LDFLAGS} -o $1 $2 ${c_addlibs}
    output_la=${1%.so}.la
    output_dir=$(dirname $1)
    ${c_dyn_linker} ${GAP_LDFLAGS} -o "$output_la" $2 ${c_addlibs} || exit 1
    if [ X"$SYS_IS_CYGWIN32" = X"yes" ] ; then
        # GAP assumes shared libraries end in .so
        for dllfile in $(cd $output_dir/.libs; ls *.dll); do
            cp $output_dir/.libs/$dllfile $output_dir/${dllfile%.dll}.so
        done
    else
        cp $output_dir/.libs/*.so $output_dir
    fi;
}


#############################################################################
##
#F  c_link <output> <inputs_o>
##
c_link () {
    echo ${c_linker} ${GAP_LDFLAGS} -o $1 $2 ${GAP_LIBS}
    ${c_linker} ${GAP_LDFLAGS} -o $1 $2 ${GAP_LIBS} || exit 1
}

#############################################################################
##
#F process_o_file <basename> <filename> 
##
## Compile according to comp_mode and comp_howfar
## 
## This does everything except the final link phase in the static case
## in that case it adds the basename and object file path of $names and $objects
##

process_o_file () {
  name=$1
  o_file=$2

  # the GAP compiler replaces _ by __ in names, so adjust here, too
  name="$(echo "$name" | sed 's/_/__/g')"

  # just remember for the linking stage later
  names="${names} ${name}"
  objects="${objects} $o_file"
}

#############################################################################
##
#F process_c_file <basename> <filename> 
##
## Compile according to comp_mode and comp_howfar
## 
## This does everything except the final link phase in the static case
## in that case it adds the basename and object file path of $names and $objects
##

process_c_file () {
    name=$1
    c_file=$2

    if [ $comp_howfar != "object" ]; then
       o_file=${gactmp}/$$_${name}.lo
       temps_o="${temps_o} ${o_file}"
    elif [ "X$output" != "X" ]; then
       o_file=$output
    else 
       o_file=${name}.lo
    fi
    c_compile $o_file $c_file "$GAP_CFLAGS"
    if [ $comp_howfar = "link" ]; then
      process_o_file $name $o_file
    fi 
}

#############################################################################
##
#F process_cxx_file <basename> <filename>
##
## Similar to process_c_file, just for C++ files.
##

process_cxx_file () {
  name=$1
  cxx_file=$2

  if [ $comp_howfar != "object" ]; then
    o_file=${gactmp}/$$_${name}.lo
    temps_o="${temps_o} ${o_file}"
  elif [ "X$output" != "X" ]; then
    o_file=$output
  else
    o_file=${name}.lo
  fi
  cxx_compile $o_file $cxx_file "$GAP_CXXFLAGS"
  if [ $comp_howfar = "link" ]; then
    process_o_file $name $o_file
  fi
}

#############################################################################
##
#F process_gap_file <filename> <ext>
##
## Compile according to comp_mode and comp_howfar
## 
## This does everything except the final link phase in the static case
## in that case it adds the basename and object file path of $names and $objects
##

process_gap_file () {
  name=$(basename $1 $2)

  if [ $comp_howfar != "c_code" ]; then
    c_file=${gactmp}/$$_${name}.c
    temps_c="${temps_c} $c_file"
  elif [ "X$output" = "X" ]; then
     c_file=${name}.c
  else
     c_file=$output
  fi
  gap_compile_in=$input
  gap_compile_name=$input  
  if [ $comp_mode = "comp_static" ]; then
    gap_compile_id=Init_${name}
    if [ $comp_static_root_relative = "yes" ]; then
        gap_compile_in=${abs_top_srcdir}/$input;
        gap_compile_name=GAPROOT/$input;
    fi
  else
    gap_compile_id=Init_Dynamic
  fi
  gap_compile $c_file ${gap_compile_in} $gap_compile_id ${gap_compile_name}
  if [ $comp_howfar != "c_code" ]; then
    process_c_file $name $c_file
    if [ "$savetemps" = "true" ]; then
        echo "Leaving C file " $c_file
    else
        echo rm -f $c_file
        rm -f $c_file
    fi
  fi
}

#############################################################################
##
#F  clean_up
##
clean_up () {
     if [ "$savetemps" = "true" ]; then
        echo "Leaving files on cleanup: " ${temps_c} ${temps_o}
    else
        echo rm -f ${temps_c} ${temps_o}
        rm -f ${temps_c} ${temps_o}
    fi
}
trap "clean_up" 2 3


#############################################################################
##
##  parse the arguments
##
if [ $# = 0 ]; then
    echo "usage: $0 [-d|-r] [-c|-C] [-o <output>] <input>..."
    exit 1
fi

comp_mode="comp_static"
comp_howfar="link"
comp_static_root_relative="no"
output=""
inputs=""
savetemps="false"

while [ $# -gt 0 ]; do
    case $1 in

    -c|--compile)         comp_howfar="object";;

    -d|--dynamic)         comp_mode="comp_dyna";;

    -C|--create-c)        comp_howfar="c_code";;

    -o|--output)          shift; output="$1";;
  
    -r)                   comp_static_root_relative="yes";;

    -save-temps)          savetemps="true";;

    -f*)                  echo "$0: no such option '$1'"
                          exit 1;;

    -k|--gap-compiler)    shift; gap_compiler="$1";;

    -p)                   shift;
                          GAP_CFLAGS="${GAP_CFLAGS} $1"
                          GAP_CXXFLAGS="${GAP_CXXFLAGS} $1"
                          ;;

    -P)                   shift; GAP_LDFLAGS="${GAP_LDFLAGS} $1";;

    -L|--addlibs)         shift; c_addlibs="${c_addlibs} $1";;

    *.g|*.gap|*.gd|*.gi|*.c|*.cc|*.cpp|*.o|*.lo)
                          inputs="${inputs} $1";;

    *)                    echo "$0: cannot handle this argument '$1'"
                          exit 1;;

    esac
    shift
done

if [ "X${inputs}" = "X" ]; then
    echo "$0: no input files given"
    exit 1
fi




#############################################################################
##
#F  make_compstat 
##

make_compstat () {
    # make 'compstat.c' and compile it
    temps_c="${temps_c} ${gactmp}/$$compstat.c"
    (
        echo     "/* made by 'gac', can be thrown away */"
        echo     "#include \"compiled.h\""

        echo     "#ifndef AVOID_PRECOMPILED"
        echo     "extern StructInitInfo * Init__type1 ( void );"
        echo     "extern StructInitInfo * Init__oper1( void );"
        echo     "#endif"

        for name in ${names}; do
            echo "extern StructInitInfo * Init__${name} ( void );"
        done

        echo     "InitInfoFunc CompInitFuncs[] = {"
        echo     "#ifndef AVOID_PRECOMPILED"
        echo     "    Init__type1,"
        echo     "    Init__oper1,"
        echo     "#endif"

        for name in ${names}; do
            echo "    Init__${name},"
        done
        echo "    0"
        echo "};"
    ) > ${gactmp}/$$compstat.c

    temps_o="${gactmp}/$$compstat.lo ${temps_o}"
    c_compile ${gactmp}/$$compstat.o ${gactmp}/$$compstat.c "${GAP_CFLAGS}"
    if [ "$savetemps" = "true" ]; then
        echo "Leaving temp file " ${gactmp}/$$compstat.c
    else
        echo rm -f ${gactmp}/$$compstat.c
        rm -f ${gactmp}/$$compstat.c
    fi
    objects="${gactmp}/$$compstat.lo ${objects}"
}

make_tmpdir () {
    if command -v mktemp >/dev/null 2>&1 ; then
        gactmp=$(mktemp -d -t "gacXXXXXXX")
    else
        basetmp=${TMPDIR:-/tmp}; #honor the TMPDIR environment variable.
        gactmp="$basetmp/gac$$";
        mkdir "$gactmp" || exit 1;
    fi
}

#############################################################################
##
##  main loop
##

#Make temporary directory
make_tmpdir;

# loop over the input files
for input in ${inputs}; do
  case $input in

        *.g) process_gap_file $input .g;;
        *.gap) process_gap_file $input .gap;;
        *.gd) process_gap_file $input .gd;;
        *.gi) process_gap_file $input .gi;;

        *.c) # compile '.c' source files
            name=$(basename ${input} .c)
            process_c_file $name $input;;

        *.cc) # compile '.cc' source files (C++)
            name=$(basename ${input} .cc)
            process_cxx_file $name $input;;

        *.cpp) # compile '.cpp' source files (also C++)
            name=$(basename ${input} .cpp)
            process_cxx_file $name $input;;

        *.cxx) # compile '.cxx' source files (also C++)
            name=$(basename ${input} .cxx)
            process_cxx_file $name $input;;

        *.o) # look over '.o' source files
            name=$(basename ${input} .o)
            process_o_file $name $input;;

        *.lo) # look over '.lo' source files
            name=$(basename ${input} .lo)
            process_o_file $name $input;;
        esac
    done


#  static link phase
if [ $comp_howfar = "link" ]; then
    if [ $comp_mode = "comp_static" ]; then
        make_compstat

        # link everything together
        if [ "X${output}" = "X" ]; then output="gacout"; fi

        for object in $GAP_OBJS;  do
            if [ ${object} != "obj/src/compstat.lo" ]; then
                objects="${abs_top_builddir}/${object} ${objects}"
            fi
        done

        c_link ${output} "${objects}"

    else
        if [ "X${output}" = "X" ]; then output="${name}.la"; fi
        c_link_dyn ${output} "${objects}"
    fi
    
    if [ "$savetemps" = "true" ]; then
        echo "Leaving object files " ${temps_o}
    else
        echo rm -f ${temps_o}
        rm -f ${temps_o}
    fi
fi

# Remove temporary directory.
# We may assume it is empty at this stage.
if [ "$savetemps" = "false" ]; then
    rm -rf "${gactmp}/.libs"
    rmdir "${gactmp}"
fi
